package com.huatai.saas.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.huatai.common.config.MinioAPI;
import com.huatai.common.constant.MinioCommonBucket;
import com.huatai.common.util.BeanUtil;
import com.huatai.saas.constant.CommonConstant;
import com.huatai.saas.dto.BackChunkDto;
import com.huatai.saas.entity.FileChunkEntity;
import com.huatai.saas.entity.FileListEntity;
import com.huatai.saas.mapper.FileChunkMapper;
import com.huatai.saas.service.FileChunkService;
import com.huatai.saas.service.FileListService;
import com.huatai.saas.vo.CheckChunkVO;
import groovy.util.logging.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @BelongsProject: nanming-saas
 * @BelongsPackage: com.huatai.saas.service.impl
 * @Author: hecaiy
 * @CreateTime: 2024-04-11  19:33
 * @Description: TODO
 * @Version: 1.0
 */
@Service
@Slf4j
public class FileChunkServiceImpl extends ServiceImpl<FileChunkMapper, FileChunkEntity> implements FileChunkService {

	@Autowired
	private MinioAPI minioAPI;

	@Autowired
	private FileListService fileListService;

	//@Value("${upload.profile}")
	private String filePath;

	private final static String folderPath = "/file";

	/**
	 * 文件上传至本地
	 * 每一个上传块都会包含如下分块信息：
	 * chunkNumber: 当前块的次序，第一个块是 1，注意不是从 0 开始的。
	 * totalChunks: 文件被分成块的总数。
	 * chunkSize: 分块大小，根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
	 * currentChunkSize: 当前块的大小，实际大小。
	 * totalSize: 文件总大小。
	 * identifier: 这个就是每个文件的唯一标示,md5码
	 * filename: 文件名。
	 * relativePath: 文件夹上传的时候文件的相对路径属性。
	 * 一个分块可以被上传多次，当然这肯定不是标准行为，但是在实际上传过程中是可能发生这种事情的，这种重传也是本库的特性之一。
	 * <p>
	 * 根据响应码认为成功或失败的：
	 * 200 文件上传完成
	 * 201 文加快上传成功
	 * 500 第一块上传失败，取消整个文件上传
	 * 507 服务器出错自动重试该文件块上传
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public int postFileUpload(BackChunkDto chunk, HttpServletResponse response) {
		int result = CommonConstant.UPDATE_FAIL;
		MultipartFile file = chunk.getFile();
		Path path = Paths.get(generatePath(filePath + folderPath, chunk));
		try {
			Files.write(path, chunk.getFile().getBytes());
			FileChunkEntity fileChunkEntity = BeanUtil.copyProperties(chunk, FileChunkEntity.class);
			result = this.baseMapper.insert(fileChunkEntity);
			//写入数据库
		} catch (IOException e) {
			e.printStackTrace();
			response.setStatus(507);
			return CommonConstant.UPDATE_FAIL;
		}
		return result;
	}

	/**
	 * 文件分片上传到minio
	 * @param chunk
	 * @param response
	 * @return
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public int postFileUploadMinio(BackChunkDto chunk, HttpServletResponse response) {
		MultipartFile file = chunk.getFile();
		int result = CommonConstant.UPDATE_FAIL;
		String originalFilename = file.getOriginalFilename();
		// 分片大于1 存在分片
		if(chunk.getTotalChunks()!=1){
			originalFilename = chunk.getChunkNumber()+file.getOriginalFilename();
		}
		try {
			String fileName = chunk.getIdentifier() + "/" +originalFilename;
			minioAPI.uploadInputStream(MinioCommonBucket.HUATAI_PUB_BUCKET_URL, fileName, file.getInputStream(), file.getContentType());
			FileChunkEntity fileChunkEntity = BeanUtil.copyProperties(chunk, FileChunkEntity.class);
			result = this.baseMapper.insert(fileChunkEntity);
			return result;
		} catch (Exception e) {
			log.error("分片文件上传异常："+e.getMessage());
			return CommonConstant.UPDATE_FAIL;
		}
	}

	/**
	 * 功能描述:生成块文件所在地址
	 *
	 * @param:
	 * @return:
	 * @author: xjd
	 * @date: 2020/7/28 16:10
	 */
	private String generatePath(String uploadFolder, BackChunkDto chunk) {
		StringBuilder sb = new StringBuilder();
		//文件夹地址/md5
		sb.append(uploadFolder).append("/").append(chunk.getIdentifier());
		//判断uploadFolder/identifier 路径是否存在，不存在则创建
		if (!Files.isWritable(Paths.get(sb.toString()))) {
			try {
				Files.createDirectories(Paths.get(sb.toString()));
			} catch (IOException e) {
				log.error(e.getMessage(), e);
			}
		}
		//文件夹地址/md5/文件名-1
		return sb.append("/")
			.append(chunk.getFileName())
			.append("-")
			.append(chunk.getChunkNumber()).toString();
	}

	/**
	 * 检查文件上传状态
	 * @param chunk
	 * @param response
	 * @return
	 */
	@Override
	public CheckChunkVO getFileUpload(BackChunkDto chunk, HttpServletResponse response) {
		CheckChunkVO vo = new CheckChunkVO();
		//检查该文件是否存在于filelist中,如果存在,直接返回skipUpload为true,执行闪传
		List<FileListEntity> backFilelists = fileListService.list(Wrappers.<FileListEntity>lambdaQuery().eq(FileListEntity::getIdentifier,chunk.getIdentifier()));
		if (backFilelists != null && !backFilelists.isEmpty()) {
			response.setStatus(HttpServletResponse.SC_CREATED);
			vo.setSkipUpload(true);
			return vo;
		}

		List<FileChunkEntity> backChunks = this.baseMapper.selectList(Wrappers.<FileChunkEntity>lambdaQuery().eq(FileChunkEntity::getIdentifier,chunk.getIdentifier()));
		//将已存在的块的chunkNumber列表返回给前端,前端会规避掉这些块
		if (backChunks != null && !backChunks.isEmpty()) {
			List<Integer> collect = backChunks.stream().map(FileChunkEntity::getChunkNumber).collect(Collectors.toList());
			vo.setUploaded(collect);
		}
		return vo;
	}

	/**
	 * 本地文件合并
	 * @param targetFile 要形成的文件名
	 * @param folder     要形成的文件夹地址
	 * @param filename   文件的名称
	 */
	@Override
	public void merge(String targetFile, String folder, String filename) {
		try {
			Files.createFile(Paths.get(targetFile));
			Files.list(Paths.get(folder))
				.filter(path -> !path.getFileName().toString().equals(filename))
				.sorted((o1, o2) -> {
					String p1 = o1.getFileName().toString();
					String p2 = o2.getFileName().toString();
					int i1 = p1.lastIndexOf("-");
					int i2 = p2.lastIndexOf("-");
					return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
				})
				.forEach(path -> {
					try {
						//以追加的形式写入文件
						Files.write(Paths.get(targetFile), Files.readAllBytes(path), StandardOpenOption.APPEND);
						//合并后删除该块
						Files.delete(path);
					} catch (IOException e) {
					}
				});
		} catch (IOException e) {}
	}

	/**
	 * bucket       桶名称
	 * chunkPath    分片文件存放路径
	 * chunchName   分片文件名称
	 * fileNamePath 合并后文件存放路径
	 * fileName	    合并后文件名称
	 * chunkNum      分片数量
	 * @return
	 */
	@Override
	public void merge(BackChunkDto chunk) throws Exception {
		String compose = minioAPI.compose(MinioCommonBucket.HUATAI_PUB_BUCKET_URL, chunk.getIdentifier(), chunk.getFileName(), CommonConstant.fileNamePath, chunk.getFileName(), chunk.getTotalChunks(), chunk.getTotalSize());
		//文件合并后保存真实路径到文件列表库
		if (compose != null) {
			FileListEntity fileListEntity = new FileListEntity();
			fileListEntity.setFileName(chunk.getFileName());
			fileListEntity.setIdentifier(chunk.getIdentifier());
			fileListEntity.setPath(compose);
			fileListEntity.setTotalSize(chunk.getTotalSize());
			this.fileListService.save(fileListEntity);
		}
	}
}
